package com.zk;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 单例zookeeper客户端
 * zookeeper客户端是不变的大对象，所以需要使用单例模式实现
 *
 * @author 007
 */
public class ZkClientHelper {

    private static final Logger log = LoggerFactory.getLogger(ZkClientHelper.class);
    /**
     * zk服务列表
     */
    public static final String ZK_CONN_STR;
    /**
     * ==========需要知道zk实现分布式锁的原理？？？？？？？？
     */
    public static final String BASE_PATH;
    /**
     * 下划线分割符
     */
    public static final String UNDER_LINE = "_";
    /**
     * session过期时间
     */
    public static final int SESSION_TIMEOUT_MS;
    /**
     * Connection连接过期时间
     */
    public static final int CONNECTION_TIMEOUT_MS;
    /**
     * 最大重试次数
     */
    public static final int RETRY_MAX_QTY;
    /**
     * 重试休眠时间
     */
    public static final int RETRY_SLEEP_MS;

    /**
     * 静态加载配置信息
     */
    static {
        try {
            PropertiesConfiguration config = new PropertiesConfiguration();
            config.setDelimiterParsingDisabled(true);
            config.setEncoding("UTF-8");
            config.setThrowExceptionOnMissing(true);

            // 要加载的配置文件
            config.load("zk2s.properties");

            // 获取zookeeper服务地址列表,多个用英文逗号隔开
            ZK_CONN_STR = config.getString("zk.server.list");

            // 获取基础地址，默认为zk
            BASE_PATH = config.getString("base.path", "/zk");

            // 获取session过期时间，默认60s
            SESSION_TIMEOUT_MS = config.getInt("session_timeout_ms", 60 * 1000);

            // 获了连接connection过期时间，默认15s
            CONNECTION_TIMEOUT_MS = config.getInt("conn_timeout_ms", 15 * 1000);

            // 获取最大重试次数，默认3次
            RETRY_MAX_QTY = config.getInt("retry_max_qty", 3);

            // 获取重试休眠时间，默认1s
            RETRY_SLEEP_MS = config.getInt("retry_sleep_ms", 1000);

        } catch (ConfigurationException e) {
            log.error("GlobalConfigurationException", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * CuratorFramework是Netflix开源的一个zookeeper客户端，
     * 比zookeeper自带的客户端使用起来容易得多
     */
    private CuratorFramework zkclient;

    /**
     * 私有构造方法
     * 单例模式必须要将构造方法私有化
     * Curator内部实现的几种重试策略:
     * 1. ExponentialBackoffRetry:重试指定的次数,且每一次重试之间停顿的时间逐渐增加
     * 2. RetryNTimes:指定最大重试次数的重试策略
     * 3. RetryOneTime:仅重试一次
     * 4. RetryUntilElapsed:一直重试直到达到规定的时间
     */
    private ZkClientHelper() {
        // 重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(0, RETRY_MAX_QTY, RETRY_SLEEP_MS);
        this.zkclient = CuratorFrameworkFactory.newClient(ZK_CONN_STR, SESSION_TIMEOUT_MS, CONNECTION_TIMEOUT_MS, retryPolicy);
        zkclient.start();
    }

    /**
     * 静态内部类
     */
    private static class ZkClientHolder {
        private static ZkClientHelper instance = new ZkClientHelper();
    }

    /**
     * 返回ZkClientHelper单例对象
     */
    private static ZkClientHelper getInstance() {
        return ZkClientHolder.instance;
    }


    /**
     * 返回 CuratorFramework
     */
    public static CuratorFramework getZkClient() {
        return ZkClientHelper.getInstance().zkclient;
    }

    /**
     * 释放Zookeeper客户端
     */
    public static void releaseZkClient() {
        log.warn("Close Curator ZkCient");
        if (ZkClientHelper.getInstance().zkclient != null) {
            // 所有实现Closeable接口的都可以这样关闭
            // 当然，如我们所知，凡是实现了Closeable接口的资源对象放在try()catch的括号里也会自动释放资源
            CloseableUtils.closeQuietly(ZkClientHelper.getInstance().zkclient);
        }
        // 置成null无引用了，加速GC
        ZkClientHelper.getInstance().zkclient = null;
    }
}
